package com.xlhl.init.aop;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.ListUtil;
import com.xlhl.init.anno.Authority;
import com.xlhl.init.common.CustomHttpCode;
import com.xlhl.init.exceptipn.BusinessException;
import com.xlhl.init.model.entity.Account;
import com.xlhl.init.model.entity.Permission;
import com.xlhl.init.model.entity.Role;
import com.xlhl.init.model.entity.RoleAssociationPermission;
import com.xlhl.init.model.enums.OneOrZeroEnums;
import com.xlhl.init.service.AccountService;
import com.xlhl.init.service.PermissionService;
import com.xlhl.init.service.RoleAssociationPermissionService;
import com.xlhl.init.service.RoleService;
import com.xlhl.init.utils.AuthUtil;
import com.xlhl.init.utils.ThrowUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 安全相关 切面
 *
 * @author xlhl
 */
@Aspect
@Component
@Slf4j
public class SecurityAspect implements Ordered {

    @Resource
    private AccountService accountService;

    @Resource
    private PermissionService permissionService;

    @Resource
    private RoleService roleService;

    @Resource
    private RoleAssociationPermissionService roleAssociationPermissionService;

    /**
     * 必须登录才能访问
     * <p>
     * 会拦截 {@link com.xlhl.init.anno.MustLogin} 注解的请求
     * </p>
     * <p>
     * 使用的 Sa-Token 进行登录 <a href='https://sa-token.cc/doc.html#/'>Sa-Token官方文档<a/>
     *
     * @param point
     */
    @Before("@annotation(com.xlhl.init.anno.MustLogin)")
    @Order(1)
    public void checkLoginStatus(JoinPoint point) {
        ThrowUtil.throwIf(!StpUtil.isLogin(), CustomHttpCode.NOT_LOGIN_ERROR);
    }

    /**
     * 自定义 权限校验 如果你要使用Sa-Token权限校验可以使用 {@link com.xlhl.init.auth.StpInterfaceImpl}
     * <p>
     * 参考 <a href='https://sa-token.cc/doc.html#/'>Sa-Token官方文档<a/> 操作
     * <p>
     * 会拦截 {@link com.xlhl.init.anno.Authority} 注解的请求
     * </p>
     * <p>
     *     获取当前登录账号角色 -> 角色是否正常？/系统角色？-> 获取注解中需要的权限 -> 获取注解需要权限的数位 -> 根据角色获取与主项权限位图 -> 依次判断
     * </p>
     *
     * @param point
     * @param authority
     */
    @Order(2)
    @Before(value = "@annotation(authority)", argNames = "point,authority")
    public void checkLoginRolePermission(JoinPoint point, Authority authority) {

        //  获取当前账号的角色信息
        long loginId = StpUtil.getLoginIdAsLong();
        Account account = accountService.lambdaQuery()
                .select(Account::getRoleId, Account::getId, Account::getStatus)
                .eq(Account::getId, loginId)
                .one();
        ThrowUtil.throwIf(account == null, CustomHttpCode.NOT_FOUND_ERROR, "未查询到当前账号的角色信息");
        ThrowUtil.throwIf(OneOrZeroEnums.isOne(account.getStatus()), "账号已被弃用");

        //  根据注解中的 权限code 获取到对应的 权限信息 包括 father_id
        String[] permissionCodes = authority.value();
        if (permissionCodes == null || permissionCodes.length == 0) {
            return;
        }
        Long roleId = account.getRoleId();
        Role role = roleService.lambdaQuery()
                .select(Role::getSystemd, Role::getStatus)
                .eq(Role::getId, roleId)
                .one();
        ThrowUtil.throwIf(role == null, CustomHttpCode.NOT_FOUND_ERROR, "未查询到当前账号的角色信息");
        if (OneOrZeroEnums.isOne(role.getSystemd())) {
            //  系统角色直接放行
            return;
        }
        ThrowUtil.throwIf(!OneOrZeroEnums.isOne(role.getStatus()), "角色已被弃用");

        //  获取注解中表示的权限对应位数
        List<Permission> permissions = permissionService.lambdaQuery()
                .select(Permission::getId, Permission::getBitPosition, Permission::getFatherId)
                .in(Permission::getCode, ListUtil.of(permissionCodes))
                .list();
        //  获取权限中的主项ID
        List<Permission> mainPermissions = permissions.stream().filter(permission -> permission.getFatherId() == -1).toList();
        //  根据角色ID与主项ID 获取 关联表中的权限位图
        List<RoleAssociationPermission> roleAssociationPermissions = roleAssociationPermissionService.lambdaQuery()
                .select(RoleAssociationPermission::getRoleId, RoleAssociationPermission::getPermissionMainId, RoleAssociationPermission::getPermission)
                .eq(RoleAssociationPermission::getRoleId, roleId)
                .in(RoleAssociationPermission::getPermissionMainId, mainPermissions.stream().map(Permission::getId).toList())
                .list();
        //  将关联表数据 转换为一个 {key: mainId, value: bitPosition} 的 Map
        HashMap<Long, Long> hash = roleAssociationPermissions.stream()
                .collect(Collectors.toMap(
                        RoleAssociationPermission::getPermissionMainId,
                        RoleAssociationPermission::getPermission,
                        // 重复时使用新值
                        (existing, replacement) -> replacement,
                        HashMap::new
                ));
        Authority.Type type = authority.type();
        boolean isAnd = type == Authority.Type.AND;

        if (isAnd) {
            // AND模式：必须所有权限都通过
            for (Permission permission : permissions) {
                //  角色与对应 main_id 的权限位图
                Long roleAssPermission = hash.getOrDefault(permission.getFatherId(), 0L);
                boolean hasPermission = AuthUtil.checkPermission(roleAssPermission, permission.getBitPosition());
                ThrowUtil.throwIf(!hasPermission, CustomHttpCode.NO_AUTH_ERROR, "无权限访问此资源");
            }
            // 所有权限都通过，放行
        } else {
            // OR模式：只要有一个权限通过就放行
            for (Permission permission : permissions) {
                //  角色与对应 main_id 的权限位图
                Long roleAssPermission = hash.getOrDefault(permission.getFatherId(), 0L);
                boolean hasPermission = AuthUtil.checkPermission(roleAssPermission, permission.getBitPosition());
                if (hasPermission) {
                    return; // 有权限，放行
                }
            }
            // 所有权限都失败，返回无权限
            throw new BusinessException(CustomHttpCode.NO_AUTH_ERROR, "无权限访问此资源");
        }
    }

    @Override
    public int getOrder() {
        return -1;
    }
}







